﻿#include <cstdio>
#include <cstring>
#include <string>
#include <shared_mutex>
#include <mutex>
#include <thread>
#include <map>
#include <queue>
#include "qqgame.h"
#include "encode.hpp"
#include "easywsclient.hpp"
#include <nlohmann/json.hpp>

#pragma comment(lib, "ws2_32")
#include <WinSock2.h>

using easywsclient::WebSocket;

struct QQGame::Impl {
    enum class State { kRunning, kClosed, kDisconnected };

    std::string gameid;
    unsigned int appid;
    std::string openid;
    std::string openkey;
    std::string pfkey;
    uint16_t port = 0;
    QQGame::Logger logger = ::printf;

    // 用于标识是否在运行中
    State state = State::kClosed;
    WebSocket *ws = nullptr;
    std::shared_mutex mutex;
    std::mutex msg_mutex;
    std::map<ObserverKey, Observer> observers;
    std::thread dispatch_thread;
    std::queue<std::string> msg_queue;

    void Init(const char *gameid, unsigned int appid, const char *openid, const char *openkey, const char *pfkey,
              uint16_t port) {
        if (ws || !gameid || !appid || !openid || !openkey || !pfkey) return;
        this->gameid = gameid;
        this->appid = appid;
        this->openid = openid;
        this->openkey = openkey;
        this->pfkey = pfkey;
        this->port = port;
        // 执行连接
        INT rc;
        WSADATA wsaData;

        rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (rc) {
            logger("WSAStartup Failed.\n");
            return;
        }
        // connect to server
        Reconnect();
    }

    bool Send(nlohmann::json &val) {
        if (val.contains("param")) {
            val["param"] = network::encoded(val["param"].dump());
        }
        return Send(val.dump());
    }

    bool Send(const std::string &msg) {
        if (!ws || msg.empty()) {
            return false;
        }
        auto state = ws->getReadyState();
        if (state == WebSocket::CLOSING || state == WebSocket::CLOSED) {
            return false;
        }
        logger("%s\n", msg.c_str());
        // 上锁
        std::lock_guard<std::mutex> lock(msg_mutex);
        msg_queue.push(msg);
        return true;
    }

    void OnMessage(MessageType type) {
        std::shared_lock<std::shared_mutex> lock(mutex);
        for (auto &p : observers) {
            p.second(type);
        }
    }

    void Reconnect() {
        if (ws) return;
        ws = WebSocket::from_url(Url());
        if (!ws) return;

        // 执行消息调度
        state = State::kRunning;
        dispatch_thread = std::thread([this] {
            while (ws->getReadyState() != WebSocket::CLOSED && (State::kRunning == state)) {
                DispatchOnce(32);
            }
            DispatchOnce(10);
            // 如果是服务端断开，那么就需要主动重置 dispatching 的值
            if (state == State::kRunning) state = State::kDisconnected;
            CloseWs();
        });
    }

    void DispatchOnce(int timeout) {
        // 发送数据
        msg_mutex.lock();
        while (msg_queue.size()) {
            ws->send(msg_queue.front());
            msg_queue.pop();
        }
        msg_mutex.unlock();
        // 读取远端数据
        ws->poll(timeout);
        ws->dispatch([this](const std::string &msg) {
            // 分析消息类型
            nlohmann::json value;
            try {
                value = nlohmann::json::parse(msg);
            } catch (const std::exception &e) {
                logger("parse error: %s\n", msg.c_str());
                return;
            }

            logger("receive msg: %s\n", msg.c_str());
            const std::string &&cmd = value["cmd"];
            MessageType msg_type = MessageType::kNone;
            static const std::vector<std::pair<std::string, MessageType> > cmds = {
                {"pay_close", MessageType::kPayClose},
                {"buy_vip_close", MessageType::kBuyVipClose},
                {"web_close", MessageType::kWebClose},
                {"bring_to_top", MessageType::kBringToTop},
            };
            if (cmd == "boss_key") {
                msg_type = bool(value["show"].get<int>()) ? MessageType::kShowGame : MessageType::kHideGame;
            } else {
                for (const auto &c : cmds) {
                    if (c.first == cmd) {
                        msg_type = c.second;
                        break;
                    }
                }
            }
            if (msg_type != MessageType::kNone) OnMessage(msg_type);
        });
    }

    void Close() {
        if (State::kRunning != state) {
            return;
        }
        state = State::kClosed;
        dispatch_thread.join();
    }

    void CloseWs() {
        if (!ws) return;
        ws->close();
        delete ws;
        ws = nullptr;
        std::lock_guard<std::mutex> lock(msg_mutex);
        while (msg_queue.size()) {
            msg_queue.pop();
        }
        // dispatch events
        OnMessage((State::kDisconnected == state) ? MessageType::kLoseConnection : MessageType::kExitGame);
    }

    std::string Url() const {
        char buf[256] = {0};
        std::snprintf(buf, sizeof(buf), "ws://127.0.0.1:%d/websocket/%s", port, openid.c_str());
        return buf;
    }

    ~Impl() { Close(); }

    nlohmann::json GetCommonParam(const char *action) const {
        nlohmann::json param;
        param["action"] = action;
        param["appid"] = appid;
        param["openid"] = openid;
        param["openkey"] = openkey;
        return param;
    }
};

QQGame::QQGame() : impl(new Impl()) {}

QQGame::~QQGame() { delete impl; }

QQGame *QQGame::Instance() {
    static QQGame *ins = new QQGame();
    return ins;
}

void QQGame::Init(const char *gameid, unsigned int appid, const char *openid, const char *openkey, const char *pfkey,
                  uint16_t port) {
    impl->Init(gameid, appid, openid, openkey, pfkey, port);
}

bool QQGame::Valid() const { return bool(impl->ws); }

const char *QQGame::openid() const { return impl->openid.c_str(); }

const char *QQGame::openkey() const { return impl->openkey.c_str(); }

const char *QQGame::pfkey() const { return impl->pfkey.c_str(); }

const char *QQGame::gameid() const { return impl->gameid.c_str(); }

unsigned int QQGame::appid() const { return impl->appid; }

void QQGame::set_pfkey(const char *key) {
    if (!key) return;
    if (impl->ws) impl->pfkey = key;
}

QQGame::ObserverKey QQGame::AddObserver(Observer &&observer) {
    std::unique_lock<std::shared_mutex> lock(impl->mutex);
    ObserverKey key = (ObserverKey)(&observer);
    impl->observers.insert({key, std::move(observer)});
    return key;
}

void QQGame::DelObserver(const ObserverKey key) {
    if (!key) return;
    std::unique_lock<std::shared_mutex> lock(impl->mutex);
    impl->observers.erase(key);
}

void QQGame::ExitGame() {
    if (auto_exit_game) {
        nlohmann::json val;
        val["cmd"] = "game_exit";
        impl->Send(val);
    }
    impl->Close();
}

void QQGame::OpenWeb(const char *title, const char *url, uint16_t width, uint16_t height) {
    if (!title || !url || !width || !height) return;

    nlohmann::json val;
    val["cmd"] = "open_web";
    val["caption"] = title;
    val["url"] = url;
    val["width"] = width;
    val["height"] = height;
    impl->Send(val);
}

void QQGame::Pay(const char *goods_url) {
    if (!goods_url) return;

    nlohmann::json val;
    val["cmd"] = "pay";
    auto common_param = impl->GetCommonParam("buy");
    common_param["sandbox"] = sandbox ? 1 : 0;
    common_param["goodstokenurl"] = goods_url;
    val["param"] = common_param;

    impl->Send(val);
}

void QQGame::BuyVip() {
    nlohmann::json val;
    val["cmd"] = "buy_vip";
    val["param"] = impl->GetCommonParam("openVip");

    impl->Send(val);
}

void QQGame::SetLogger(const Logger logger) {
    if (!logger) {
        return;
    }
    impl->logger = logger;
}